// Test for the util::socket client socket wrapper
// Copyright 2004 (C) Ralph Thomas

#include <util/socket.h>
#include <tests/testCore.h>

#ifdef UNIX
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#ifndef sockaddr_in
#include <netinet/in.h>
#endif
#endif

#ifdef _WINDOWS
#include <winsock2.h>
#define socklen_t int
#endif

//
/// The testSocket class exercises the util::socket at connecting,
/// disconnecting, sending and recieving.
//
class testSocket : public test::basic {
  public:
	const char* performTest() {
		//
		// Try to get it to connect to a (hopefully closed) port.
		//
		if( util::socket::fromHostnameAndPort( "localhost", 2323 ) ) return "socket connected to a closed port";
		//
		// Try to connect to a non existant host
		//
		if( util::socket::fromHostnameAndPort( "monkey-not-here.infinite-imagination.com", 80 ) ) return "socket connected to non-existant host";
		//
		// Give it rubbish
		//
		if( util::socket::fromHostnameAndPort( "shajajfd'!@()#'dasda", 8080 ) ) return "socket connected to rubbish string";
#ifdef _WINDOWS
		int error;
		WORD winSockVersion;
		WSADATA winSockData;

		winSockVersion = MAKEWORD( 2, 2 );
		error = WSAStartup( winSockVersion, &winSockData );
		if( error == WSAVERNOTSUPPORTED ) return "couldn't start winsock";
#endif
		//
		// Open up a listening socket and get the client object to
		// connect to it. This test is unfortunately UNIX specific.
		//
		struct sockaddr_in servaddr;
		memset( &servaddr, 0, sizeof( struct sockaddr_in ) );
		int fd = socket( AF_INET, SOCK_STREAM, 0 );
		if( fd == -1 ) return "couldn't create a sock_stream for the client to connect to!";
		servaddr.sin_family = AF_INET;
		servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
		servaddr.sin_port = htons( 2323 );
		if( bind( fd, (struct sockaddr*) &servaddr, sizeof( servaddr ) ) != 0 ) return "couldn't bind to socket on port 2323 for the client to connect to!";
		if( listen( fd, 5 ) != 0 ) return "couldn't listen for client connections";

		//
		// Now we're all set to get the socket class to connect to the
		// port we just opened. Note that this test will fail on
		// machines with firewalls blocking the loopback network device.
		//
		util::socket* cli = util::socket::fromHostnameAndPort( "localhost", 2323 );
		if( !cli ) return "couldn't create util::socket for opened port";
		//
		// Accept the (hopefully) incoming connection.
		//
		struct sockaddr cliaddr;
		socklen_t clilen = sizeof( cliaddr );
		
		int cliFd = accept( fd, (struct sockaddr*) &cliaddr, &clilen );
		if( cliFd < 0 ) return "client socket didn't contact us";
#ifdef UNIX
		shutdown( fd, SHUT_RDWR );
		close( fd );
#endif
#ifdef _WINDOWS
		closesocket( fd );
#endif

		//
		// Make sure that the client doesn't think it has any data
		// waiting to be read.
		//
		if( cli->isDataAvailable( 0 ) ) return "client socket thinks that data is available before we have sent anything";

		//
		// Write something down our socket, and see if the client can
		// read it.
		//
		if( send( cliFd, "message!", strlen( "message!" ), 0 ) < 1 ) return "couldn't write to client";
		//
		// Make sure that the client now thinks that there is data
		// available.
		//
		if( !cli->isDataAvailable( 0 ) ) return "client socket does not think that there is data available after we sent data";
		//
		// Read the data and check that it's the same as what we sent.
		//
		util::string msg;
		if( cli->recieve( msg, strlen( "message!" ) ) != util::socket::kOk ) return "client couldn't read!";
		if( msg != "message!" ) return "client did not read expected message from socket";
		//
		// Check that we read all the data (and that no extra characters
		// were appended to what got sent).
		//
		if( cli->isDataAvailable( 0 ) ) return "client socket thinks data may still be available after it should have read it all";

		//
		// Write something with a newline, and check that that's all the
		// client reads.
		//
		msg = "this message\nhas a newline in";
		if( send( cliFd, msg.c_str(), msg.size(), 0 ) < 1 ) return "coultn'd write to client the second time";
		if( cli->recieveLine( msg ) != util::socket::kOk ) return "client couldn't read a line";
		if( msg != "this message\n" ) return "client did not read expected line";

		//
		// Test that the client can send data to us.
		//
		msg = "sending message";
		char buffer[16]; buffer[15] = 0;
		if( cli->send( msg ) != util::socket::kOk ) return "client couldn't send data";
		if( recv( cliFd, buffer, 16, 0 ) < 1 ) return "couldn't read data sent by client";
		if( msg != buffer ) return "read unexpected data from client";

		//
		// Close our end of the socket and check that the client returns
		// error for recieve and send.
		//
#ifdef UNIX
		shutdown( cliFd, SHUT_RDWR );
		close( cliFd );
#endif
#ifdef _WINDOWS
		closesocket( cliFd );
#endif
		delete cli;
		return NULL;
	}

	const char* getTestName() {
		return "Test exercising client capabilities of util::socket";
	}
};

void test::runnerSingleton::registerTests() {
	adoptTest( new testSocket() );
}

